并发编程(1) 您所在的位置:网站首页 cpu 编程 并发编程(1)

并发编程(1)

#并发编程(1)| 来源: 网络整理| 查看: 265

原子性高频问题 1.java中如何实现线程安全? 

多线程操作共享数据出现的问题。

锁:

悲观锁:synchronized,lock(AQS实现)

乐观锁:CAS

可以根据业务情况,选择ThreadLocal。

2.CAS底层实现 

 java角度:先比较值是否一致,一致则交换,返回true;否则,返回false

native调用了本地依赖库中的C++中的方法——unsafe方法

 在CAS底层,如果是多核系统,增加了一个lock指令,这里使用了C++的内联汇编

汇编指令cmpxchg, CPU硬件底层就支持 比较和交换 (cmpxchg),cmpxchg并不保证原子性的。(cmpxchg的操作是不能再拆分的指令)所以才会出现判断CPU是否是多核,如果是多核就追加lock指令。lock指令可以理解为是CPU层面的锁,一般锁的粒度就是 缓存行 级别的锁,当然也有 总线锁 ,但是成本太高,CPU会根据情况选择。

3.CAS的ABA问题

此类问题常出现在++,- -的操作中。

线程A:期望将value从A1 - B2

线程B:期望将value从B2 - A3

线程C:期望将value从A1 - C4

从原子性来考虑,是无法保证线程安全的,如何解决此类问题呢?在java的JUC下AtomicStampedReference中提供了解决方案,即在修改value的同时,指定好版本号。

可见性问题 1.Java的内存模型

JMM,不是JVM内存模型!!!

缓存是CPU的缓存,CPU的缓存分为L1(线程独享),L2(内核独享),L3(多核共享)

JMM就是Java内存模型的核心,可见性,有序性都基于这实现。

主内存JVM,就是你堆内存。

 在处理指令时,CPU会拉取数据,优先级是从L1到L2到L3,如果都没有,需要去主内存中拉取,JMM就是在CPU和主内存之间,来协调,保证可见、有序性等操作。

2.保证可见性的方式

可见性是指线程间的,对变量的变化是否可见

Java层面:

volatile,用volatile基本数据类型,可以保证每次CPU去操作数据时,都直接去主内存进行读写。

synchronized,synchronized的内存语义可以保证在获取锁之后,可以保证前面操作的数据是可见的。

lock(CAS-volatile),也可以保证CAS或者操作volatile的变量之后,可以保证前面操作的数据是可见的。

final,是常量没法动~~

3.有了MESI,为什么还有volatile?

MESI是CPU缓存一致性的协议,大多数的CPU厂商都根据MESI去实现了缓存一致性的效果。

CPU已经有MESI协议了,volatile是不是有点多余啊!?

首先,这俩不冲突,一个是从CPU硬件层面上的一致性,一个是Java中JMM层面的一致性。

MESI协议,有一套固定的机制,无论是否声明了volatile,都会基于这个机制来保证缓存的一致性(可见性)。同时,也要清楚,如果没有MESI协议,volatile也会存在一些问题,不过也有其他的处理方案(总线锁,时间成本太高了,如果锁了总线,就一个CPU核心在干活)。

MESI是协议,是规划,是interface,他需要CPU厂商实现。

既然CPU有MESI了,为啥还要volatile,那自然是MESI协议有问题。MESI保证了多核CPU的独占cache之间的可见性,但是CPU不是说必须直接将寄存器中的数据写入到L1,因为在大多是×86架构的CPU中,寄存器和L1之间有一个store buffer,寄存器值可能落到了store buffer,没落到L1中,就会导致缓存不一致。而且除了×86架构的CPU,在arm和power的CPU中,还有load buffer,invalid queue都会或多或少影响缓存一致性!

有序性 1.什么是有序性

单例模式中的懒汉机制中,就存在一个这样的问题。

懒汉为了保证线程安全,一般会采用DCL的方式。

但是单单用DCL,依然会有几率出现问题。

线程可能会拿到初始化一半的对象去操作,极有可能出现NullPointException。

(初始化对象三部,开辟空间,初始化内部属性,指针指向引用)

在Java编译.java为.class时,会基于JIT做优化,将指令的顺序做调整,从而提升执行效率。

在CPU层面,也会对一些执行进行重新排序,从而提升执行效率。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有